package ch.sfug.anim.tween.filter {
    import ch.sfug.anim.tween.property.PropertyTween;
    import ch.sfug.events.TweenEvent;
    import ch.sfug.util.RGB;
    
    import flash.display.DisplayObject;
    import flash.events.EventDispatcher;
    import flash.filters.ColorMatrixFilter;      

    /**
     * this is a tween that the color matrix.
     * 
     * @author marcel
     */
    public class ColorMatrixTween extends EventDispatcher {
        
        private var t               :DisplayObject;
        private var tmatrix         :Array;
        private var _duration       :Number;
        private var pt              :PropertyTween;
        private var _cmtx			:ColorMatrixFilter;

        public var m0				:Number;
        public var m1				:Number;
        public var m2				:Number;
        public var m3				:Number;
        public var m4				:Number;
        public var m5				:Number;
        public var m6				:Number;
        public var m7				:Number;
        public var m8				:Number;
        public var m9				:Number;
        public var m10				:Number;
        public var m11				:Number;
        public var m12				:Number;
        public var m13				:Number;
        public var m14				:Number;
        public var m15				:Number;
        public var m16				:Number;
        public var m17				:Number;
        public var m18				:Number;
        public var m19				:Number;


               
        private var _lumR:Number = 0.212671;
		private var _lumG:Number = 0.715160;
		private var _lumB:Number = 0.072169;
		
		private var _ID_MATRIX:Array = [1,0,0,0,0,
										0,1,0,0,0,
										0,0,1,0,0,
										0,0,0,1,0,
										0,0,0,0,1];
		
		private static const DELTA_INDEX:Array = [
			0,    0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1,  0.11,
			0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
			0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
			0.44, 0.46, 0.48, 0.5,  0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 
			0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
			1.0,  1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
			1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0,  2.12, 2.25, 
			2.37, 2.50, 2.62, 2.75, 2.87, 3.0,  3.2,  3.4,  3.6,  3.8,
			4.0,  4.3,  4.7,  4.9,  5.0,  5.5,  6.0,  6.5,  6.8,  7.0,
			7.3,  7.5,  7.8,  8.0,  8.4,  8.7,  9.0,  9.4,  9.6,  9.8, 
			10.0
		];
		
       public function ColorMatrixTween( dis:DisplayObject, d:Number ) 
       {
        	target     = dis;
        	_duration  = d;
            pt = new PropertyTween(this, d);
            pt.addEventListener(TweenEvent.TWEEN_UPDATE, updateFilters);
            pt.addEventListener(TweenEvent.TWEEN_END, 	 end);
            initMatrix();
       }
       
		  
        public function initMatrix():void
        {           
        	
            var found:Boolean = false;
            var objFilters:Array = target.filters;

            for (var i:Number = 0; i < objFilters.length; i++) 
            {
                if (objFilters[i] is ColorMatrixFilter) {
                	_cmtx 	= new ColorMatrixFilter( objFilters[i].matrix );
                	tmatrix	= objFilters[i].matrix;
                    objFilters.splice( i, 1, _cmtx );
                    found = true;
                }
            } 
           
           if (!found) 
           {
                tmatrix 						= getdefaultMatrix();
                _cmtx					 		= new ColorMatrixFilter( tmatrix );
                objFilters[objFilters.length] 	= _cmtx;
                
            }

            target.filters = objFilters;
            
           //
        }
        

        public function updateFilters( event:TweenEvent ):void
        {
        	var m:Array = [m0,m1,m2,m3,m4,
						   m5,m6,m7,m8,m9,
						   m10,m11,m12,m13,m14,
						   m15,m16,m17,m18,m19]
        	
        	setObjectMatrix( target, m );
        }
        
        public function endMatrix():Array
        {
        	var matrix:Array = _ID_MATRIX.slice();
        	
        	if (_brightness != 1) 
        	{
        		matrix = setBrightness( matrix, _brightness );
        	}
        	
        	if (_contrast != 1)
        	{
	        	matrix = setContrast( matrix, _contrast );
        	}
        	
        	if (_hue != 0 )
        	{
        		matrix = setHue( matrix, _hue );
        	}
        	
        	if (_saturation != 1)
        	{
        		matrix = setSaturation( matrix, _saturation );
        	}
        	
        	        	
        	if (_color != 0xFFFFFF)
        	{
        		matrix = colorize( matrix, Math.round(_color), _amount );
        	}
        	
        	return matrix;
        }
        
        public  var _color		    :Number         = 0xFFFFFF;
        public  var _amount			:Number         = 1;
        public  var _brightness     :Number         = 1;
        public  var _contrast       :Number         = 1;
        public  var _saturation     :Number         = 1;
        public  var _hue            :Number         = 1;
        public  var _threshold		:Number         = -1;
        
        public function colorMatrixTo( color:uint=0xFFFFFF, 
        							   amount:Number = 1, 
        							   saturation:Number=1, 
        							   contrast:Number=1, 
        							   brightness:Number=1, 
        							   hue:Number=0, 
        							   ease:Function = null ):void
        {
			
			
			_color		= color;
        	_amount		= amount;
        	_hue		= hue;
        	_contrast	= contrast;
        	_saturation = saturation;
        	_brightness = brightness;
        	
        	var em:Array = endMatrix()
        	
         	pt.tween( { m0:      { from: tmatrix[0],      to: em[0],      easing: ease},
         			    m1:      { from: tmatrix[1],      to: em[1],      easing: ease},
         			    m2:      { from: tmatrix[2],      to: em[2],      easing: ease},
         			    m3:      { from: tmatrix[3],      to: em[3],      easing: ease},
         			    m4:      { from: tmatrix[4],      to: em[4],      easing: ease},
         			    m5:      { from: tmatrix[5],      to: em[5],      easing: ease},
         			    m6:      { from: tmatrix[6],      to: em[6],      easing: ease},
         			    m7:      { from: tmatrix[7],      to: em[7],      easing: ease},
         			    m8:      { from: tmatrix[8],      to: em[8],      easing: ease},
         			    m9:      { from: tmatrix[9],      to: em[9],      easing: ease},
         			    m10:     { from: tmatrix[10],      to: em[10],      easing: ease},
         			    m11:     { from: tmatrix[11],      to: em[11],      easing: ease},
         			    m12:     { from: tmatrix[12],      to: em[12],      easing: ease},
         			    m13:     { from: tmatrix[13],      to: em[13],      easing: ease},
         			    m14:     { from: tmatrix[14],      to: em[14],      easing: ease},
         			    m15:     { from: tmatrix[15],      to: em[15],      easing: ease},
         			    m16:     { from: tmatrix[16],      to: em[16],      easing: ease},
         			    m17:     { from: tmatrix[17],      to: em[17],      easing: ease},
         			    m18:     { from: tmatrix[18],      to: em[18],      easing: ease},
         			    m19:     { from: tmatrix[19],      to: em[19],      easing: ease}
        	          }); 
         }
       
               
        /**
         * returns the displayobject where the filter is applied on
         */
         
        private function setObjectMatrix(p_obj:Object, p_matrix:Array): void 
        {
            var objFilters:Array = p_obj.filters;
            var found:Boolean = false;
            for (var i:Number = 0; i < objFilters.length; i++) {
                if (objFilters[i] is ColorMatrixFilter) {
                    ColorMatrixFilter( _cmtx ).matrix = p_matrix;
                    objFilters.splice( i, 1, _cmtx );
                    found = true;
                }
            }
            
            p_obj.filters = objFilters; 
            
        }
        
        /**
         * returns the displayobject where the filter is applied on
         */
         
         private function setBrightness($m:Array, $n:Number):Array {
			
			$n = cleanValue($n,100);
			
			if (isNaN($n)) {
				return $m;
			}

			return applyMatrix([1,0,0,0,$n,
								0,1,0,0,$n,
								0,0,1,0,$n,
								0,0,0,1,0], $m);
		}
		
         private function colorize($m:Array, $color:Number, $amount:Number = 1):Array 
         { 
			if (isNaN($color)) {
				return $m;
			} else if (isNaN($amount)) {
				$amount = 1;
			}
			
			var rgb:RGB = RGB.createFromNumber( $color );
			
 			var r:Number = (($color >> 16) & 0xff) / 255;
			var g:Number = (($color >> 8)  & 0xff) / 255;
			var b:Number = ($color         & 0xff) / 255;
 			/* var r:Number = rgb.red;
			var g:Number = rgb.green;
			var b:Number = rgb.blue; */
						
			
			var inv:Number = 1 - $amount;
			var temp:Array =  [inv + $amount * r * _lumR, $amount * r * _lumG,       $amount * r * _lumB,       0, 0,
							  $amount * g * _lumR,        inv + $amount * g * _lumG, $amount * g * _lumB,       0, 0,
							  $amount * b * _lumR,        $amount * b * _lumG,       inv + $amount * b * _lumB, 0, 0,
							  0, 				          0, 					     0, 					    1, 0];
							  		
			return applyMatrix(temp, $m);
		}
		
		
		public function setSaturation($m:Array, $n:Number):Array 
		{
			if (isNaN($n)) {
				return $m;
			}
			
			$n = cleanValue($n,100);
			var x:Number = 1+(($n > 0) ? 3*$n/100 : $n/100);

			
			var temp:Array = [_lumR*(1-x)+x,_lumG*(1-x),_lumB*(1-x),0,0,
							  _lumR*(1-x),_lumG*(1-x)+x,_lumB*(1-x),0,0,
							  _lumR*(1-x),_lumG*(1-x),_lumB*(1-x)+x,0,0,
							  0,0,0,1,0];
			
			return applyMatrix(temp, $m);
		}
		
		
		public function setHue($m:Array, $n:Number):Array 
		{
			if (isNaN($n)) {
				return $m;
			}
			
			$n = cleanValue($n,180)/180*Math.PI;
			var cosVal:Number = Math.cos($n);
			var sinVal:Number = Math.sin($n);

			var temp:Array = [_lumR+cosVal*(1-_lumR)+sinVal*(-_lumR),_lumG+cosVal*(-_lumG)+sinVal*(-_lumG),_lumB+cosVal*(-_lumB)+sinVal*(1-_lumB),0,0,
							  _lumR+cosVal*(-_lumR)+sinVal*(0.143),_lumG+cosVal*(1-_lumG)+sinVal*(0.140),_lumB+cosVal*(-_lumB)+sinVal*(-0.283),0,0,
							  _lumR+cosVal*(-_lumR)+sinVal*(-(1-_lumR)),_lumG+cosVal*(-_lumG)+sinVal*(_lumG),_lumB+cosVal*(1-_lumB)+sinVal*(_lumB),0,0,
							  0,0,0,1,0];

			return applyMatrix(temp, $m);
			
		}
		
		private function setContrast($m:Array, $n:Number):Array 
		{
			if (isNaN($n)) {
				return $m;
			}
			
			$n = cleanValue($n,100);

			var x:Number;
			if ($n<0) {
				x = 127+$n/100*127
			} else {
				x = $n%1;
				if (x == 0) {
					x = DELTA_INDEX[$n];
				} else {
					x = DELTA_INDEX[($n<<0)]*(1-x)+DELTA_INDEX[($n<<0)+1]*x;
				}
				x = x*127+127;
			}
			
			var temp:Array = [x/127,0,0,0,0.5*(127-x),
							  0,x/127,0,0,0.5*(127-x),
							  0,0,x/127,0,0.5*(127-x),
							  0,0,0,1,0];
							  
			return applyMatrix(temp, $m);
		}
		
		private function cleanValue(p_val:Number,p_limit:Number):Number 
		{
			return Math.min(p_limit,Math.max(-p_limit,p_val));
		}
		
		private function applyMatrix($m:Array, $m2:Array):Array 
		{
			if (!($m is Array) || !($m2 is Array)) {
				return $m2;
			}
			var temp:Array = [];
			var i:int = 0;
			var z:int = 0;
			var y:int, x:int;
			for (y = 0; y < 4; y++) {
				for (x = 0; x < 5; x++) {
					if (x == 4) {
						z = $m[i + 4];
					} else {
						z = 0;
					}
					temp[i + x] = $m[i]   * $m2[x]      + 
								  $m[i+1] * $m2[x + 5]  + 
								  $m[i+2] * $m2[x + 10] + 
								  $m[i+3] * $m2[x + 15] +
								  z;
				}
				i += 5;
			}
			return temp;
		}
		
        /**
         * returns the displayobject where the filter is applied on
         */
         
        public function stop():void 
        {
           pt.stop();
        }
        
        public function get target():DisplayObject {
            return t;
        }
        
        public function set target( trg:DisplayObject ):void {
            this.t = trg;
        }
        
        public function get duration():Number {
            return _duration;
        }
        
        public function set duration( d:Number ):void {
            _duration   = d;
            pt.duration = duration;
        }
        
        public function end( event:TweenEvent ):void {
            dispatchEvent( event );
        }
        
        		        
        private function getdefaultMatrix():Array 
        {
            return [
                1, 0, 0, 0, 0,
                0, 1, 0, 0, 0,
                0, 0, 1, 0, 0,
                0, 0, 0, 1, 0
            ];
        }
        
    }
}
